package com.orientechnologies.orient.object.jpa;

import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.metamodel.Metamodel;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.object.db.OObjectDatabaseTx;

/**
 * @author Sergey Bespalov
 *
 */
public class OJPAObjectDatabaseTxEntityManager implements EntityManager
{

    /** the log used by this class. */
    private static Logger logger = Logger.getLogger(OJPAPersistenceProvider.class.getName());

    private final EntityManagerFactory emFactory;
    private final OObjectDatabaseTx database;
    private final EntityTransaction transaction;
    private final OJPAProperties properties;
    private FlushModeType flushMode = FlushModeType.AUTO;

    public OJPAObjectDatabaseTxEntityManager(OObjectDatabaseTx database,
                                             EntityManagerFactory entityManagerFactory,
                                             OJPAProperties properties)
    {
        this.properties = properties;
        this.emFactory = entityManagerFactory;
        this.database = database;
        this.transaction = new OJPAEntityTransaction(database);

        if (logger.isLoggable(Level.INFO))
        {
            logger.fine("EntityManager created for persistence unit : " + entityManagerFactory.toString());
        }
    }

    @Override
    public void persist(Object entity)
    {
        database.save(entity);
    }

    @Override
    public <T> T merge(T entity)
    {
        throw new UnsupportedOperationException("merge");
    }

    @Override
    public void remove(Object entity)
    {
        database.delete(entity);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T find(Class<T> entityClass,
                      Object primaryKey)
    {
        final ORecordId rid;

        if (primaryKey instanceof ORecordId)
        {
            rid = (ORecordId) primaryKey;
        }
        else if (primaryKey instanceof String)
        {
            rid = new ORecordId((String) primaryKey);
        }
        else if (primaryKey instanceof Number)
        {
            // COMPOSE THE RID
            OClass cls = database.getMetadata().getSchema().getClass(entityClass);
            if (cls == null)
            {
                throw new IllegalArgumentException("Class '" + entityClass + "' is not configured in the database");
            }
            rid = new ORecordId(cls.getDefaultClusterId(), ((Number) primaryKey).longValue());
        }
        else
        {
            throw new IllegalArgumentException(
                    "PrimaryKey '" + primaryKey + "' type (" + primaryKey.getClass() + ") is not supported");
        }

        return (T) database.load(rid);
    }

    @Override
    public <T> T getReference(Class<T> entityClass,
                              Object primaryKey)
    {
        throw new UnsupportedOperationException("merge");
    }

    @Override
    public void flush()
    {
        if (flushMode == FlushModeType.COMMIT)
        {
            database.commit();
            if (logger.isLoggable(Level.FINEST))
            {
                logger.fine("EntityManager flushed. " + toString());
            }
        }
    }

    @Override
    public FlushModeType getFlushMode()
    {
        return flushMode;
    }

    @Override
    public void setFlushMode(FlushModeType flushMode)
    {
        this.flushMode = flushMode;
    }

    @Override
    public void lock(Object entity,
                     LockModeType lockMode)
    {
        throw new UnsupportedOperationException("lock");
    }

    @Override
    public void refresh(Object entity)
    {
        database.load(entity);
        if (logger.isLoggable(Level.FINEST))
        {
            logger.fine("EntityManager refreshed. " + toString());
        }
    }

    @Override
    public void clear()
    {
        if (flushMode == FlushModeType.COMMIT)
        {
            database.rollback();
            if (logger.isLoggable(Level.FINEST))
            {
                logger.fine("EntityManager cleared. " + toString());
            }
        }
    }

    @Override
    public boolean contains(Object entity)
    {
        return database.isManaged(entity);
    }

    @Override
    public Query createQuery(String qlString)
    {
        throw new UnsupportedOperationException("createQuery");
    }

    @Override
    public Query createNamedQuery(String name)
    {
        throw new UnsupportedOperationException("createNamedQuery");
    }

    @Override
    public Query createNativeQuery(String sqlString)
    {
        throw new UnsupportedOperationException("createNativeQuery");
    }

    @Override
    @SuppressWarnings("rawtypes")
    public Query createNativeQuery(String sqlString,
                                   Class resultClass)
    {
        throw new UnsupportedOperationException("createNativeQuery");
    }

    @Override
    public Query createNativeQuery(String sqlString,
                                   String resultSetMapping)
    {
        throw new UnsupportedOperationException("createNativeQuery");
    }

    @Override
    public StoredProcedureQuery createNamedStoredProcedureQuery(String s)
    {
        throw new UnsupportedOperationException("createNamedStoredProcedureQuery");
    }

    @Override
    public StoredProcedureQuery createStoredProcedureQuery(String s)
    {
        throw new UnsupportedOperationException("createStoredProcedureQuery");
    }

    @Override
    public StoredProcedureQuery createStoredProcedureQuery(String s, Class... classes)
    {
        throw new UnsupportedOperationException("createStoredProcedureQuery");
    }

    @Override
    public StoredProcedureQuery createStoredProcedureQuery(String s, String... strings)
    {
        throw new UnsupportedOperationException("createStoredProcedureQuery");
    }

    @Override
    public void joinTransaction()
    {
        throw new UnsupportedOperationException("joinTransaction");
    }

    @Override
    public boolean isJoinedToTransaction()
    {
        return false;
    }

    @Override
    public <T> T find(Class<T> entityClass,
                      Object primaryKey,
                      Map<String, Object> properties)
    {
        throw new UnsupportedOperationException("find(Class<T>, LockModeType, Map<String, Object>)");
    }

    @Override
    public <T> T find(Class<T> entityClass,
                      Object primaryKey,
                      LockModeType lockMode)
    {
        throw new UnsupportedOperationException("find(Class<T>, Object, LockModeType");
    }

    @Override
    public <T> T find(Class<T> entityClass,
                      Object primaryKey,
                      LockModeType lockMode,
                      Map<String, Object> properties)
    {
        throw new UnsupportedOperationException("find(Class<T>, Object, LockModeType, Map<String, Object>)");
    }

    @Override
    public void lock(Object entity,
                     LockModeType lockMode,
                     Map<String, Object> properties)
    {
        throw new UnsupportedOperationException("lock");

    }

    @Override
    public void refresh(Object entity,
                        Map<String, Object> properties)
    {
        throw new UnsupportedOperationException("refresh");

    }

    @Override
    public void refresh(Object entity,
                        LockModeType lockMode)
    {
        throw new UnsupportedOperationException("refresh");

    }

    @Override
    public void refresh(Object entity,
                        LockModeType lockMode,
                        Map<String, Object> properties)
    {
        throw new UnsupportedOperationException("refresh");

    }

    @Override
    public void detach(Object entity)
    {
        throw new UnsupportedOperationException("detach");
    }

    @Override
    public LockModeType getLockMode(Object entity)
    {
        throw new UnsupportedOperationException("getLockMode");
    }

    @Override
    public void setProperty(String propertyName,
                            Object value)
    {
        throw new UnsupportedOperationException("setProperty");
    }

    @Override
    public Map<String, Object> getProperties()
    {
        return properties.getUnmodifiableProperties();
    }

    @Override
    public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery)
    {
        throw new UnsupportedOperationException("createQuery");
    }

    @Override
    public Query createQuery(CriteriaUpdate criteriaUpdate)
    {
        throw new UnsupportedOperationException("createQuery");
    }

    @Override
    public Query createQuery(CriteriaDelete criteriaDelete)
    {
        throw new UnsupportedOperationException("createQuery");
    }

    @Override
    public <T> TypedQuery<T> createQuery(String qlString,
                                         Class<T> resultClass)
    {
        throw new UnsupportedOperationException("createQuery");
    }

    @Override
    public <T> TypedQuery<T> createNamedQuery(String name,
                                              Class<T> resultClass)
    {
        throw new UnsupportedOperationException("createNamedQuery");
    }

    @Override
    public <T> T unwrap(Class<T> cls)
    {
        throw new UnsupportedOperationException("unwrap");
    }

    @Override
    public EntityManagerFactory getEntityManagerFactory()
    {
        return emFactory;
    }

    @Override
    public CriteriaBuilder getCriteriaBuilder()
    {
        throw new UnsupportedOperationException("getCriteriaBuilder");
    }

    @Override
    public Metamodel getMetamodel()
    {
        throw new UnsupportedOperationException("getMetamodel");
    }

    @Override
    public <T> EntityGraph<T> createEntityGraph(Class<T> aClass)
    {
        throw new UnsupportedOperationException("createEntityGraph");
    }

    @Override
    public EntityGraph<?> createEntityGraph(String s)
    {
        throw new UnsupportedOperationException("createEntityGraph");
    }

    @Override
    public EntityGraph<?> getEntityGraph(String s)
    {
        throw new UnsupportedOperationException("getEntityGraph");
    }

    @Override
    public <T> List<EntityGraph<? super T>> getEntityGraphs(Class<T> aClass)
    {
        throw new UnsupportedOperationException("getEntityGraphs");
    }

    @Override
    public Object getDelegate()
    {
        return database;
    }

    @Override
    public EntityTransaction getTransaction()
    {
        return transaction;
    }

    @Override
    public void close()
    {
        database.close();
        if (logger.isLoggable(Level.INFO))
        {
            logger.fine("EntityManager closed. " + toString());
        }
    }

    @Override
    public boolean isOpen()
    {
        return !database.isClosed();
    }

    @Override
    public String toString()
    {
        return "EntityManager for User@Database:" + database.getUser() + "@" + database.getURL() + ", "
                + super.toString();
    }

}
